use std::fmt::{Display, Formatter};
use std::fs;
use serde::{Serialize, Serializer};
use serde::ser::SerializeStruct;
use crate::RowData;

pub trait Countable {
    fn record(&mut self, star: u8) -> ();
}
struct PoolDiceData<const N: usize> {
    total:  usize,
    five:   [usize; N],
    four:   [usize; N],
    three:  [usize; N],

    counter:   u8,
}
impl<const N: usize> PoolDiceData<N> {
    pub fn new() -> Self {
        PoolDiceData {
            total:  0,
            five:   [0; N],
            four:   [0; N],
            three:  [0; N],

            counter: 0,
        }
    }
    pub fn count(&mut self, star: u8) -> u8 {
        assert!(star <= 5 || star >= 3);
        let star = star as usize;
        let count = self.counter;
        if star == 5 {
            self.counter = 0;
        }
        self.total += 1;
        self.counter += 1;
        count
    }
    pub fn append(&mut self, other: &PoolDiceData<N>) -> () {
        for i in 0..N {
            self.total      += other.total;
            self.five[i]    += other.five[i];
            self.four[i]    += other.four[i];
            self.three[i]   += other.three[i];
        }
    }
}
impl<const N: usize> Display for PoolDiceData<N> {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "\nFive: {:?}\nFour: {:?}\nThree:{:?}\n", self.five, self.four, self.three)
    }
}
impl<const N: usize> Countable for &mut PoolDiceData<N> {
    fn record(&mut self, star: u8) -> () {
        let mut location = self.count(star) as usize;
        if location >= N {
            return
        }
        match star {
            3 => self.three[location] += 1,
            4 => self.four[location] += 1,
            5 => self.five[location] += 1,
            _ => unreachable!()
        }
    }
}
impl<const N: usize> Serialize for PoolDiceData<N> {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer {
        let mut state = serializer.serialize_struct("PoolDiceData", 4)?;
        state.serialize_field("total", &self.total)?;
        state.serialize_field("five", &self.five.to_vec())?;
        state.serialize_field("four", &self.four.to_vec())?;
        state.serialize_field("three", &self.three.to_vec())?;
        state.end()
    }
}

#[derive(Serialize)]
pub struct DiceData {
    newbie:     PoolDiceData<20>,
    regular:    PoolDiceData<90>,
    character1: PoolDiceData<90>,
    character2: PoolDiceData<90>,
    weapon:     PoolDiceData<80>,
}
impl DiceData {
    pub fn new() -> Self {
        DiceData {
            newbie:     PoolDiceData::<20>::new(),
            regular:    PoolDiceData::<90>::new(),
            character1: PoolDiceData::<90>::new(),
            character2: PoolDiceData::<90>::new(),
            weapon:     PoolDiceData::<80>::new(),
        }
    }

    pub fn append(&mut self, other: &Self) -> () {
        self.newbie     .append(&other.newbie);
        self.regular    .append(&other.regular);
        self.character1 .append(&other.character1);
        self.character2 .append(&other.character2);
        self.weapon     .append(&other.weapon);
    }

    pub fn get(&mut self, type_id: u16) -> Box<dyn Countable + '_> {
        match type_id {
            100 => Box::from(&mut self.newbie),
            200 => Box::from(&mut self.regular),
            301 => Box::from(&mut self.character1),
            400 => Box::from(&mut self.character2),
            302 => Box::from(&mut self.weapon),
            _ => unreachable!()
        }
    }

    pub fn record(&mut self, row_data: &RowData) -> () {
        let mut pool = self.get(row_data.gacha_type);
        pool.record(row_data.rank_type)
    }

    pub fn write_to_json(&self, path: &str) -> () {
        let json_data = serde_json::to_string(&self).unwrap();
        fs::write("dice_data.json", json_data).unwrap();
    }
}
impl Display for DiceData {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "Newbie:{}Regular:{}Chara1:{}Chara2:{}Weapon:{}",
            self.newbie,
            self.regular,
            self.character1,
            self.character2,
            self.weapon,
        )
    }
}
